package com.zkh.myutils.json;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.zkh.myutils.utils.ArrayUtils;
import com.zkh.myutils.utils.TypeUtils;

/**
 * json 字符串解析器
 * @author zkh
 */
public class JSONStringParser {

	/**
	 * 解析JSON字符串为Map集合
	 * @return
	 */
	static Map<String, Object> parseToMap(Character[] charArr) {
		//容器
		HashMap<String, Object> map = new HashMap<String, Object>();
		//索引
		int[] startEnd = {0, ArrayUtils.indexOf('{', charArr)};
		//检查是否直接结束
		if(!isEnd(charArr, startEnd)) {
			//从'{'开始解析
			do{
				//获取key范围
				startEnd = findKeyRange(charArr, startEnd[1] + 1);
				//获取key
				String key = substring(charArr, startEnd[0], startEnd[1]);
				//为空
				if(key.trim().isEmpty()){
					throw new JSONParseException("key不能为空");
				}
				//解析值时，跳过:
				toValueIndex(charArr, startEnd);
				//解析并保存键值
				map.put(key, parseValue(charArr, startEnd));
			}while(hasNext(charArr, startEnd));
		}else {
			startEnd[1]++;
		}
		//结束标识
		if(!charArr[startEnd[1]].equals('}')) {
			throw new JSONParseException("JSON字符串缺失“}”符号");
		}
		if(hasOtherChar(charArr, startEnd[1])) {
			throw new JSONParseException("JSON字符串最后存在非法字符");
		}
		//返回容器
		return map;
	}

	/**
	 * 解析JSON字符串为List集合
	 * @return
	 */
	static List<Object> parseToList(Character[] charArr){
		//容器
		ArrayList<Object> list = new ArrayList<Object>();
		//索引
		int[] startEnd = {0, ArrayUtils.indexOf('[', charArr)};
		//检查是否直接结束
		if(!isEnd(charArr, startEnd)) {
			//从'['开始解析
			do{
				//首次，跳过“[”，非首次，跳过逗号
				startEnd[1]++;
				//解析并保存值
				list.add(parseValue(charArr, startEnd));
			}while(hasNext(charArr, startEnd));
		}else {
			startEnd[1]++;
		}
		//结束标识
		if(!charArr[startEnd[1]].equals(']')) {
			throw new JSONParseException("JSON字符串缺失“]”符号");
		}
		if(hasOtherChar(charArr, startEnd[1])) {
			throw new JSONParseException("JSON字符串最后存在非法字符");
		}
		//返回容器
		return list;
	}

	/**
	 * 是否直接结束，开始时检查
	 * @param charArr json串
	 * @param startEnd 开始结束索引
	 * @return
	 */
	private static boolean isEnd(Character[] charArr, int[] startEnd) {
		//遍历
		for(int i=startEnd[1] + 1, len=charArr.length; i<=len; i++) {
			//当前字符
			Character curr = charArr[i];
			//除空格、“}”和“]”字符外，还存在其它字符，则说明不是空JSON串
			if(curr.equals(' ')) {
				continue;
			}
			return curr.equals('}') || curr.equals(']');
		}
		//不会执行到
		return false;
	}

	/**
	 * 查询key索引范围（已经去除“{”字符）
	 * @param index 索引
	 * @return
	 */
	private static int[] findKeyRange(Character[] charArr, int index) {
		//索引
		int[] startEnd = {0, 0};
		//当前是否查找开始
		boolean findStart = true;
		//转义符号数量
		int escape = 0;
		//try如果没有匹配到完整的双引号，将抛出异常
		try {
			//遍历
			for(int i=index, len=charArr.length; i<=len; i++) {
				//检测到引号（当引号前面存在奇数个转义符号时，引号要当作普通字符）
				if(charArr[i].equals('"') && escape%2==0) {
					if(findStart) {
						//开始索引（去掉引号）
						startEnd[0] = i + 1;
						//查找标识为查找结束
						findStart = false;
					}else {
						//结束标识
						startEnd[1] = i;
						//返回（正常的json串，总会在此处结束）
						return startEnd;
					}
					//重置转义符号数量
					escape=0;
				}else {
					escape = charArr[i].equals('\\') ? escape + 1 : 0;
				}
			}
		}catch(ArrayIndexOutOfBoundsException e) {
			throw new JSONParseException("JSON格式错误，解析失败");
		}
		//此处不可能会执行到，但必须要有
		return startEnd;
	}
	
	/**
	 * 解析值。如果值为对象或数组，则递归调用parseToMap或parseToList
	 * @param startEnd key的开始结束索引，用来存放value的开始结束索引
	 * @return
	 */
	private static Object parseValue(Character[] charArr, int[] startEnd) {
		/*
		 * JSON的值只会存在以下6种情况，其它均视为无效的json串：
		 * 1、字符串（被双引号包裹）；2、数字（纯数字）；3、数组（被“[]”包裹）；
		 * 4、对象（被“{}”包裹）；5、布尔值（true或false，必须小写）；6、null（必须小写）
		 */
		//遍历
		for(int i=startEnd[1],len=charArr.length; i<len; i++) {
			//当前值
			Character curr = charArr[i];
			//跳过空格
			if(curr.equals(' ') || curr.equals('\t')) {
				continue;
			}
			//开始字符索引
			int start = i;
			//数字(包括+-号)
			if((curr>='0' && curr<='9') || curr=='-' || curr=='+' || curr=='.') {
				//点次数
				int dot = 0;
				//点
				if(curr=='.') {
					dot++;
				}
				//后续
				for(i=i+1; i<len; i++) {
					//当前值
					curr = charArr[i];
					//数字，则继续
					if((curr>='0' && curr<='9') || (dot==0 && curr=='.')) {
						if(curr=='.') {
							dot++;
						}
						continue;
					}
					//数字已结束
					if(curr==' ' || curr==',' || curr==']' || curr=='}') {
						//保存索引
						startEnd[0] = start;startEnd[1] = i-1;
						//获取字符
						String number = substring(charArr, start, i);
						//只有正负
						if("+".equals(number) || "-".equals(number)) {
							throw new JSONParseException("非法字符“" + number + "”");
						}
						//获取数字
						return new BigDecimal(number);
					}else {
						throw new JSONParseException("非法字符“" + curr + "”");
					}
				}
				//标准json格式不会执行到此处
				throw new JSONParseException("JSON格式错误，解析失败");
			}else {
				//非数字
				switch(curr.charValue()) {
					case '"'://字符串
						//获取字符串
						int[] range = findKeyRange(charArr, start);
						//保存索引
						startEnd[0] = start;startEnd[1] = range[1];
						//返回值
						return substring(charArr, start+1, range[1]);
					case '['://数组
					case '{'://对象
						//数组
						boolean arr = curr.charValue()=='[';
						//目标值
						char trgL = arr ? '[' : '{';
						char trgR = arr ? ']' : '}';
						//'['嵌套个数
						int count = 0;
						//将值解析成完整的对象
						for(i=i+1; i<len; i++) {
							//当前字符
							curr = charArr[i];
							//嵌套
							if(curr==trgL) {
								count++;	//嵌套数加1
							}else if(curr==trgR){
								if(count==0) {
									//保存索引
									startEnd[0] = start;startEnd[1] = i;
									//子对象/数组字符串
									String trg = substring(charArr, start, i + 1);
									//转Character[]
									Character[] ca = TypeUtils.toWrap(trg.toCharArray());
									//返回值
									return arr ? parseToList(ca) : parseToMap(ca);
								}else {
									count--;
								}
							}
						}
						//标准json格式不会执行到此处
						throw new JSONParseException("JSON格式错误，解析失败");
					case 't'://true
						//解析true
						String tbool = substring(charArr, start, start+4);
						//是否为true
						if("true".equals(tbool)) {
							//保存索引
							startEnd[0] = start;startEnd[1] = start+3;
							//返回
							return true;
						}else {
							throw new JSONParseException("JSON格式错误，解析失败");
						}
					case 'f'://false
						//解析false
						String fbool = substring(charArr, start, start+5);
						//是否为true
						if("false".equals(fbool)) {
							//保存索引
							startEnd[0] = start;startEnd[1] = start+4;
							//返回
							return false;
						}else {
							throw new JSONParseException("JSON格式错误，解析失败");
						}
					case 'n'://null
						//解析null
						String nul = substring(charArr, start, start+4);
						//是否为true
						if("null".equals(nul)) {
							//保存索引
							startEnd[0] = start;startEnd[1] = start+3;
							//返回
							return null;
						}else {
							throw new JSONParseException("JSON格式错误，解析失败");
						}
					default://其它非法字符
						throw new JSONParseException("非法字符“" + curr + "”");
				}
			}
		}
		return null;
	}

	/**
	 * 解析值前，跳过帽号 
	 * @param charArr json串
	 * @param startEnd 开始结束索引
	 */
	private static void toValueIndex(Character[] charArr, int[] startEnd) {
		//遍历(跳过末尾引号)
		for(int i=startEnd[1]+1,len=charArr.length; i<len; i++) {
			//当前字符
			Character curr = charArr[i];
			//空格
			if(curr.equals(' ')) {
				continue;
			}
			if(curr.equals(':')) {
				startEnd[1]=i+1;return;
			}else {
				throw new JSONParseException("缺失字符“:”");
			}
		}
	}

	/**
	 * 是否还有键/值，均根据逗号判定<br />
	 * 该方法必然在解析值之后才会调用到，startEnd[1]保存的是值的最后一个字符的索引，所以要+1
	 * @return
	 */
	private static boolean hasNext(Character[] charArr, int[] startEnd) {
		//遍历逗号（只能存在空格、逗号、“]” 和 “}”符号，其它字符直接抛出异常）
		for(int i=startEnd[1] + 1,len=charArr.length; i<len; i++) {
			//当前字符
			Character curr = charArr[i];
			//检查格式
			if(curr.equals(']') || curr.equals('}')) {
				//保存索引
				startEnd[1] = i;
				//返回状态
				return false;
			}else if(curr.equals(' ')) {
				continue;
			}else if(curr.equals(',')){
				//保存索引
				startEnd[1] = i;
				//返回状态
				return true;
			}else{
				throw new JSONParseException("JSON格式错误，解析失败");
			}
		}
		throw new JSONParseException("JSON格式错误，解析失败");
	}

	/**
	 * 已经解析到闭合符号，后面还有非空字符
	 * @param charArr json串
	 * @param end 闭合符号的位置索引
	 * @return
	 */
	private static boolean hasOtherChar(Character[] charArr, int end) {
		//遍历
		for(int i=end+1, len=charArr.length; i<len; i++) {
			if(!charArr[i].equals(' ')) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 截取数组拼接成字符串
	 */
	private static String substring(Character[] arr, int start, int end) {
		//拼接容器
		StringBuilder sb = new StringBuilder();
		//遍历
		for(int i=start; i<end; i++) {
			sb.append(arr[i]);
		}
		//返回字符串
		return sb.toString();
	}
}
